package controllers.system;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.fasterxml.jackson.databind.JsonNode;


import models.dao.CommonDAO;
import models.dao.ModuleDAO;
import models.dao.ModuleResourceDAO;
import models.dao.ModuleStaffDAO;
import models.dao.ModuleUriDAO;
import models.entities.Module;
import models.entities.ModuleResource;
import models.entities.ModuleStaff;
import models.entities.ModuleUri;
import models.entities.Staff;
import models.enumtype.ActiveType;
import models.enumtype.KPaging;
import models.enumtype.MethodType;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.io.ZipInputStream;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.unzip.UnzipUtil;

import controllers.Authentication;
import controllers.SessionManager;
import play.Play;
import play.data.DynamicForm;
import play.data.Form;
import play.i18n.Messages;
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Http.MultipartFormData;
import play.mvc.Http.MultipartFormData.FilePart;
import play.mvc.Result;
import play.mvc.Security.Authenticated;
import services.ModuleMgr;
import utils.ErrorJSON;
import utils.StringUtil;

@org.springframework.stereotype.Controller
@Authenticated(Authentication.class)
public class System  extends Controller {
	@Autowired
	ModuleMgr moduleMgr;
	private final int BUFF_SIZE = 4096;
	
	public Result index() {
  		Map<String, Object> loginStaff = SessionManager.get("loginStaff"); 
    	return ok(views.html.system.index.render(loginStaff));
  	}
	
	public Result deleteStaff() {
		DynamicForm dynamicForm = Form.form().bindFromRequest();
		String _moduleId = dynamicForm.get("moduleId");
		Long moduleId = null;
		try {
			moduleId = Long.parseLong(_moduleId);
		} catch (Exception e) {
			moduleId = null;
		}
		String _staffId = dynamicForm.get("staffId");
		Long staffId = null;
		try {
			staffId = Long.parseLong(_staffId);
		} catch (Exception e) {
			staffId = null;
		}
		try {
			ModuleStaff moduleStaff = moduleMgr.getModuleStaffByModuleIdStaffId(moduleId, staffId);
			if(moduleStaff == null) {
				return ok(Json.toJson(new ErrorJSON(Messages.get("error.system"))));
			}
			moduleMgr.deleteModuleStaff(moduleStaff);
		} catch (Exception e) {
			return ok(Json.toJson(new ErrorJSON(Messages.get("error.system"))));
		}
		return ok(Json.toJson(new ErrorJSON(Messages.get("success.delete.message"), false)));
	}
	
	public Result addStaff2Module() {
		return ok();
	}
	
	public Result getListStaff() {
		DynamicForm dynamicForm = Form.form().bindFromRequest();
		String staffCode = dynamicForm.get("staffCode");
		String staffName = dynamicForm.get("staffName");
		
		String _page = dynamicForm.get("page");
		Integer page = null;
		try {
			page = Integer.parseInt(_page);
		} catch (Exception e) {
			page = null;
		}
		
		String _rows = dynamicForm.get("rows");
		Integer rows = null;
		try {
			rows = Integer.parseInt(_rows);
		} catch (Exception e) {
			rows = null;
		}
		
		KPaging<Staff> kPaging = null;
		if(page != null && rows != null) {
			kPaging = new KPaging<Staff>();
		}
		
		try {
			List<Staff> lst = moduleMgr.getListStaff(kPaging, staffCode, staffName, ActiveType.RUNNING);
			if(kPaging == null) {
				return ok(Json.toJson(lst));
			} else {
				Map<String, Object> result = new HashMap<String, Object>();
				result.put("total", lst.size());
				result.put("rows", lst);
				return ok(Json.toJson(result));
			}
		} catch (Exception e) {
			if(kPaging == null) {
				return ok(Json.toJson(new ArrayList<Staff>()));
			} else {
				Map<String, Object> result = new HashMap<String, Object>();
				result.put("total", 0);
				result.put("rows", new ArrayList<Staff>());
				return ok(Json.toJson(result));
			}
		}
	}
	
	public Result getListModuleStaff() {
		DynamicForm dynamicForm = Form.form().bindFromRequest();
		String _moduleId = dynamicForm.get("moduleId");
		Long moduleId = null;
		try {
			moduleId = Long.parseLong(_moduleId);
		} catch (Exception e) {
			moduleId = null;
		}
		if(moduleId == null) {
			return ok(Json.toJson(new ArrayList<ModuleStaff>()));
		}
		try {
			List<ModuleStaff> lst = moduleMgr.getListModuleStaff(null, moduleId);
			return ok(Json.toJson(lst));
		} catch (Exception e) {
			return ok(Json.toJson(new ArrayList<ModuleStaff>()));
		}
	}
	
	public Result getListModule() {
		DynamicForm dynamicForm = Form.form().bindFromRequest();
		String moduleCode = dynamicForm.get("moduleCode");
		String moduleName = dynamicForm.get("moduleName");
		String _status = dynamicForm.get("status");
		Integer __status = null;
		ActiveType status = null;
		
		try {
			__status = Integer.parseInt(_status);
			status = ActiveType.parseValue(__status);
		} catch (Exception e) {
			__status = null;
			status = null;
		}
		
		String _page = dynamicForm.get("page");
		Integer page = null;
		try {
			page = Integer.parseInt(_page);
		} catch (Exception e) {
			page = null;
		}
		
		String _rows = dynamicForm.get("rows");
		Integer rows = null;
		try {
			rows = Integer.parseInt(_rows);
		} catch (Exception e) {
			rows = null;
		}
		
		KPaging<Module> kPaging = null;
		if(page != null && rows != null) {
			kPaging = new KPaging<Module>();
			kPaging.setPageSize(rows);
			kPaging.setPage(page - 1);
		}
		try {
			List<Module> lstModule = moduleMgr.getListModule(kPaging, moduleCode, moduleName, status);
			if(kPaging != null) {
				Map<String, Object> result = new HashMap<String, Object>();
				result.put("total", lstModule.size());
				result.put("rows", lstModule);
				return ok(Json.toJson(result));
			} else {
				return ok(Json.toJson(lstModule));
			}
		} catch (Exception e) {
			if(kPaging != null) {
				Map<String, Object> result = new HashMap<String, Object>();
				result.put("total", 0);
				result.put("rows", new ArrayList<Module>());
				return ok(Json.toJson(result));
			} else {
				return ok(Json.toJson(new ArrayList<Module>()));
			}
		}
	}
	
	public Result upload() {
		char anortherSeperate = '\\';
		if(File.separatorChar == '\\') {
			anortherSeperate = '/';
		} else {
			anortherSeperate = '\\';
		}
		try {
			MultipartFormData formUpload = request().body().asMultipartFormData();
			
			FilePart zipFile = formUpload.getFile("zipFile");
			
			String zipFileName = zipFile.getFilename().substring(0, zipFile.getFilename().length() - 4);
			
			File _zipFile = zipFile.getFile();
			
			String tmpDir = Play.application().path() + File.separator + "tmp";//thu muc tmp de extract file
			
			File __tmpDir = new File(tmpDir);
			
			if(!__tmpDir.exists()) {
				__tmpDir.mkdir();
			}
			
			String appDir = Play.application().path() + File.separator + "app";//thu muc de copy file vao
			
			String tmpOutputDir = tmpDir + File.separator + zipFileName;
			
			ZipFile extractFile = new ZipFile(_zipFile);
			
			List<FileHeader> lstFileHeader = extractFile.getFileHeaders();
			
			FileHeader installXML = extractFile.getFileHeader("install.xml");
			
			ZipInputStream isInstallXML = extractFile.getInputStream(installXML);
			
			Map<String, Object> cfg = getListResourcerFormXML(isInstallXML);
			
			if(cfg == null) {
				return ok(Json.toJson("Loi"));
			}
			
			File applicationPathFile = Play.application().path();
			
			boolean isExistInstallFile = false;
			
			Map<String, Object> resouceMap = (Map<String, Object>) cfg.get("resource");
			
			for(int i = 0; i < lstFileHeader.size(); i++) {
				String outFilePath = tmpOutputDir + File.separator + lstFileHeader.get(i).getFileName();
				File outFile = new File(outFilePath);
				if(lstFileHeader.get(i).isDirectory() || lstFileHeader.get(i).getFileName().equals("install.xml")) {
					continue;
				} else {
					File fileCheck = new File(appDir + File.separator + lstFileHeader.get(i).getFileName());
					if(resouceMap.get(lstFileHeader.get(i).getFileName().replace(anortherSeperate, File.separatorChar)) != null) {
						if(fileCheck.exists()) {
							Map<String, Object> result = new HashMap<String, Object>();
							result.put("error", true);
							result.put("errMsg", "Chức năng đã tồn tại");
							return ok(Json.toJson(result));
						}
						createFile(outFile);
					} else {
						continue;
					}
				}
				
				ZipInputStream is = extractFile.getInputStream(lstFileHeader.get(i));
				
				OutputStream os = new FileOutputStream(outFile);
				
				int readLen = -1;
				byte[] buff = new byte[BUFF_SIZE];
				
				while ((readLen = is.read(buff)) != -1) {
					os.write(buff, 0, readLen);
				}
				
				is.close();
				os.close();
				
				UnzipUtil.applyFileAttributes(lstFileHeader.get(i), outFile);
			}
			
			copyAll2Directory(tmpDir, appDir);
			FileUtils.deleteDirectory(__tmpDir);
			
			Module module = (Module) cfg.get("module");
			Map<String, Object> resourceMap = (Map<String, Object>) cfg.get("resource");
			Set<String> lstKey = resourceMap.keySet();
			List<ModuleResource> lstModuleResouce = new ArrayList<ModuleResource>();
			for(String resourceUrl : lstKey) {
				ModuleResource mr = (ModuleResource) resouceMap.get(resourceUrl);
				lstModuleResouce.add(mr);
			}
			List<ModuleUri> lstResouceUri = (List<ModuleUri>) cfg.get("route");
			moduleMgr.installModule(module, lstModuleResouce, lstResouceUri);
			String contentRoute = "\n#" + module.getModuleCode() + "\n";
			
			for(ModuleUri uri : lstResouceUri) {
				contentRoute += uri.getMethod() + "\t" + uri.getUri() + "\t" + uri.getFunction() + "\n";
			}
			
			contentRoute += "#" + module.getModuleCode() + "\n";
			
			String pathRouteFile = Play.application().path() + File.separator + "conf" + File.separator + "routes";
			File routeFile = new File(pathRouteFile);
			if(routeFile.exists()) {
				FileWriter fileWriter = new FileWriter(routeFile, true);
				BufferedWriter buffWriter = new BufferedWriter(fileWriter);
				buffWriter.write(contentRoute);
				buffWriter.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return ok(Json.toJson("Hello World!"));
	}
	
	private Map<String, Object> getListResourcerFormXML(ZipInputStream is) throws SAXException, IOException, ParserConfigurationException {
		char anortherSeperate = '\\';
		if(File.separatorChar == '\\') {
			anortherSeperate = '/';
		} else {
			anortherSeperate = '\\';
		}
		Boolean index = false;
		Map<String, Object> result = new HashMap<String, Object>();
		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();
		Document doc = dbBuilder.parse(is);
		doc.getDocumentElement().normalize();
		
		NodeList appCode = doc.getElementsByTagName("app-code");
		Node _appCode = appCode.item(0);
		String __appCode = _appCode.getTextContent().trim();
		if(__appCode.contains(" ")) {
			return null;
		}
		
		NodeList appName = doc.getElementsByTagName("app-name");
		Node _appName = appName.item(0);
		String __appName = _appName.getTextContent().trim();
		
		NodeList description = doc.getElementsByTagName("description");
		Node _description = description.item(0);
		String __description = _description.getTextContent().trim();
		
		NodeList version = doc.getElementsByTagName("version");
		Node _version = version.item(0);
		String __version = _version.getTextContent();
		
		Module module = new Module();
		module.setModuleCode(__appCode);
		module.setModuleName(__appName);
		module.setDescription(__description);
		module.setVersion(__version);
		
		result.put("module", module);
		
		NodeList installFile = doc.getElementsByTagName("install-file");
		Node _installFile = installFile.item(0);
		NodeList installFileChild = _installFile.getChildNodes();
		Map<String, Object> resourceMap = new HashMap<String, Object>();
		result.put("resource", resourceMap);
		for(int i = 0; i < installFileChild.getLength(); i++) {
			Node node = installFileChild.item(i);
			if(node.getNodeType() == Node.ELEMENT_NODE) {
				if(node.getNodeName().equals("controllers")) {
					NodeList controllerNode = node.getChildNodes();
					for(int j = 0; j < controllerNode.getLength(); j++) {
						Node file = controllerNode.item(j);
						if(file.getNodeType() == Node.ELEMENT_NODE) {
							String url = file.getTextContent();
							url = url.trim();
							url = url.replace(anortherSeperate, File.separatorChar);
							if(url.contains(" ")) {
								Map<String, Object> error = new HashMap<String, Object>();
								error.put("error", true);
								error.put("errMsg", "Lỗi. Một chức năng không thể có quá 2 trang index");
								return error;
							}
							ModuleResource urlFile = new ModuleResource();
							urlFile.setUrl(url);
							resourceMap.put(url, urlFile);
						}
					}
				} else if(node.getNodeName().equals("services")) {
					NodeList serviceNode= node.getChildNodes();
					for(int j = 0; j < serviceNode.getLength(); j++) {
						Node file = serviceNode.item(j);
						if(file.getNodeType() == Node.ELEMENT_NODE) {
							String url = file.getTextContent();
							url = url.trim();
							url = url.replace(anortherSeperate, File.separatorChar);
							if(url.contains(" ")) {
								return null;
							}
							ModuleResource urlFile = new ModuleResource();
							urlFile.setUrl(url);
							resourceMap.put(url, urlFile);
						}
					}
				} else if(node.getNodeName().equals("models")) {
					NodeList controllerNode = node.getChildNodes();
					for(int j = 0; j < controllerNode.getLength(); j++) {
						Node file = controllerNode.item(j);
						if(file.getNodeType() == Node.ELEMENT_NODE) {
							String url = file.getTextContent();
							url = url.trim();
							url = url.replace(anortherSeperate, File.separatorChar);
							if(url.contains(" ")) {
								return null;
							}
							ModuleResource urlFile = new ModuleResource();
							urlFile.setUrl(url);
							resourceMap.put(url, urlFile);
						}
					}
				} else if(node.getNodeName().equals("views")) {
					NodeList controllerNode = node.getChildNodes();
					for(int j = 0; j < controllerNode.getLength(); j++) {
						Node file = controllerNode.item(j);
						if(file.getNodeType() == Node.ELEMENT_NODE) {
							String url = file.getTextContent();
							url = url.trim();
							url = url.replace(anortherSeperate, File.separatorChar);
							if(url.contains(" ")) {
								return null;
							}
							ModuleResource urlFile = new ModuleResource();
							urlFile.setUrl(url);
							resourceMap.put(url, urlFile);
						}
					}
				}
			}
		}
		
		NodeList routeUrl = doc.getElementsByTagName("route");
		Node _routeUrl = routeUrl.item(0);
		NodeList listUrl = _routeUrl.getChildNodes();
		List<ModuleUri> listRoute = new ArrayList<ModuleUri>();
		result.put("route", listRoute);
		for(int i = 0; i < listUrl.getLength(); i++) {
			Node url = listUrl.item(i);
			if(url.getNodeType() == Node.ELEMENT_NODE) {
				if(url.getNodeName().equals("url")) {
					ModuleUri objUri = new ModuleUri();
					Element __url = (Element) url;
					if(!index && !StringUtil.isNullOrEmpty(__url.getAttribute("index"))
							&& Boolean.parseBoolean(__url.getAttribute("index")) == Boolean.TRUE) {
						index = true;
						objUri.setIdx(1);
					} else if(index && !StringUtil.isNullOrEmpty(__url.getAttribute("index"))
							&& Boolean.parseBoolean(__url.getAttribute("index")) == Boolean.TRUE) {
						Map<String, Object> error = new HashMap<String, Object>();
						error.put("error", true);
						error.put("errMsg", "Lỗi. Một chức năng không thể có quá 2 trang index");
						return error;
					}
					NodeList urlNode = url.getChildNodes();
					String method = "";
					String uri = "";
					String function = "";
					for(int j = 0; j < urlNode.getLength(); j++) {
						Node nodeUrl = urlNode.item(j);
						if(nodeUrl.getNodeType() == Node.ELEMENT_NODE 
								&& (nodeUrl.getNodeName().equals("method") || nodeUrl.getNodeName().equals("uri") || nodeUrl.getNodeName().equals("function"))) {
							if(nodeUrl.getNodeName().equals("method")) {
								method = nodeUrl.getTextContent().trim();
							} else if(nodeUrl.getNodeName().equals("uri")) {
								uri = nodeUrl.getTextContent().trim();
							} else if(nodeUrl.getNodeName().equals("function")) {
								function = nodeUrl.getTextContent();
							}
						}
					}
					objUri.setMethod(MethodType.parseValue(method));
					objUri.setFunction(function);
					objUri.setUri(uri);
					listRoute.add(objUri);
				}
			}
		}
		return result;
	}
	
	private void copyAll2Directory(String srcFileName, String descFileName) throws IOException {
		File srcFile = new File(srcFileName);
		File descFile = new File(descFileName);
		File[] lstSub = srcFile.listFiles();
		for(File f : lstSub) {
			FileUtils.copyDirectory(f, descFile);
		}
	}
	
	private File createFile(String filePath) throws IOException {
		File file = new File(filePath);
		File parentDir = file.getParentFile();
		Stack<File> stackMkdir = new Stack<File>();
		while(!parentDir.exists()) {
			stackMkdir.push(parentDir);
			parentDir = parentDir.getParentFile();
		}
		while(!stackMkdir.isEmpty()) {
			File dir = stackMkdir.pop();
			dir.mkdir();
		}
		if(!file.exists()) {
			file.createNewFile();
		}
		return file;
	}
	
	private File createFile(File file) throws IOException {
		File parentDir = file.getParentFile();
		Stack<File> stackMkdir = new Stack<File>();
		while(!parentDir.exists()) {
			stackMkdir.push(parentDir);
			parentDir = parentDir.getParentFile();
		}
		while(!stackMkdir.isEmpty()) {
			File dir = stackMkdir.pop();
			dir.mkdir();
		}
		if(!file.exists()) {
			file.createNewFile();
		}
		return file;
	}
}
